package edu.psu.geovista.app.plot.ruler;

import edu.psu.geovista.app.pcp.toolkits.Math.JMath;
import edu.psu.geovista.app.plot.axis.BasicAxis;
import edu.psu.geovista.app.plot.axis.Range;
import edu.psu.geovista.app.plot.ruler.tick.Tick;

import java.awt.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Arrays;

/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
 * All Rights Researved.
 *
 * This library is developed and distributed for free usage,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * You can redistribute and/or modify it under the terms of the
 * Simplified BSD License
 *
 *
 *
 *
 *
 *
 *
 * Description
 *
 * Date: May 31, 2003
 * Time: 3:20:10 PM
 * @author Jin Chen
 */

public class DfRuler implements Ruler /*extends ComponentUI*/{

        public static final boolean DEBUG=false;

        private int labelScale=JMath.LABEL_x1;  // 

        // data transfer
       /**
         * valueAt0 is  the  real data value that on the O point of drawing coordinate.
         * By default, it is max of tickRange.max. If make axis up side down, the valueAt0=tickRange.min
         */
        private float valueAt0;   //ypixel= =y0 *ks - RawY*ks
        private float ks=1;//scale for mapping data display range to axis display range. By default, do not scale(ks=1)
        private Range tickRange;//=new Extreme();;
        private boolean ascend;// 
        private int orient;//vertical or Horizontal
        private float[] ticksvalue;// 

        //GUI  state: should contained in UI, but may need dynamically switch UI, so keep here
        private Point location;//starting point for drawing
        protected  int length;
        private Color color; //ruler's color
        private BasicStroke stroke= new BasicStroke(1); //control line width,
        private Color labelColor;   //text label's color
        private Font labelFont;//=new Font("Helvetica",Font.ITALIC,14);
        //ticks
        //protected int tickL=10;//long tick length
        //protected int tickS=5;
        //protected int numOfSection=6;
        //protected float[] tickValues=new float[numOfSection+1];;

        //protected int soffx=10; //string Offset X

        private boolean showTick=true;//if true draw tick and label value
        private boolean showLabel=true;
        //private int numOfDigit=2;// number of digits to the right of the decimal point. It is for setting label value.

        private int format=JMath.FORMAT_DIGIT;

        PropertyChangeSupport pcs=new PropertyChangeSupport(this);//fired whenever data model (tickRange, orient, location,length...) changed

    RulerUI ui;
    public DfRuler() {
        
        this(Ruler.VERTICAL, 200, new Point(),new Range(100,0));
    }

    public DfRuler(int orient, int length, Point op,Range range) {
             init();
            this.setup(orient, length, op,range)  ;
        }
    protected void init(){
        tickRange=new Range();
        labelFont=new Font("Helvetica",Font.ITALIC,14);
    }
        /**
         *  subclass must call this method.
         *  Invoked before paint to initial value
         */
        protected void setup(BasicAxis ax){

            this.setup(ax.getOrient(), ax.getActiveSize(), ax.getAxisLocation(),ax.getTickRange());
        }
        protected void setup(int orient, int length, Point op, Range range) {

            this.length =length;//must set before set tickrange, since setTickRange() will calculate scale

            this.setTickRange(range);

            //ui=new
            this.setOrient(orient);

            this.setLocation(op);// location=op;

            //tickValues
            installUI();
        }

    protected void installUI() {
        this.ui = createDefaultUI(this);
        this.pcs.addPropertyChangeListener((PropertyChangeListener)ui);
    }
    protected RulerUI createDefaultUI(Ruler r){
        return   new DfRulerUI(r);
    }
    /*******************************************************************************************************
     *                Logic
     *******************************************************************************************************/
    /**
     *  Given a real value, rule the graphic loc of the value
     */
    public int getGraphicValueOf(float realValue){
        //float realValue=(float) Math.log(realvalue);
        //float valueAt0=this.valueAt0 ;
        if(Float.isNaN(realValue) ) {
            //log
            throw new IllegalArgumentException("Unable to calculate on NaN value");
        }
        float value=0;
        //float scaledy=realy*ks;//scale , must do scale before transform
        if (this.orient ==HORIZONTAL){
            value=(realValue-valueAt0)*ks+this.location.x ; //transform
        }
        else if(this.orient ==VERTICAL){
          value=(valueAt0-realValue)*ks+this.location.y ; //transform
        }
        return Math.round(value);

    }
     /**
     *  Given a graphic value, rule the real value
     *  @param  gv  the graphic value
     */
    public float getValueOf(float gv){
        float value=Float.NaN;
        //float scaledy=realy*ks;//scale , must do scale before transform
        if (this.orient ==HORIZONTAL){
            value=(gv-this.location.x)/ks+this.valueAt0 ;

            //value=(realValue-this.valueAt0)*ks+this.location.x ; //transform
        }
        else if(this.orient ==VERTICAL){
            value=+this.valueAt0 -(gv-this.location.y)/ks;
          //value=(this.valueAt0-realValue)*ks+this.location.y ; //transform
        }
        return value;

    }
    /**
     *
     * @param deltaV   change of graphic value
     * @return         change of real value correpsonding to deltaV
     */
    public float getValueOfchange(float deltaV) {
        return deltaV/ks;
    }


     public void setTickRange(Range range) {
       
       /* this.tickRange.setExtreme(range);
       //reset y0
         updateValueAt0();
         this.calculateScale() ;*/
         boolean updated = this.setTickRange(range.getMin() ,range.getMax());
         if(updated){
            this.pcs.firePropertyChange(Ruler.PROPERTY_TICKRANGE,null,range);
         }
     }
     //never make it public since it won't fire event as the public method. This is only for programing convenience
     protected boolean setTickRange(float min, float max){

         boolean updated = this.tickRange.setExtreme(min,max);//
          if(updated){//to improve performance, 
            updateValueAt0();
            this.calculateScale() ;
         }
         return updated;
     }

    private void updateValueAt0() {
        Range range=this.getTickRange() ;
        if (this.isAscend() ){
            this.setValueAt0(range.getMin() );
        }
        else{
             this.setValueAt0(range.getMax() );
        }
    }

    public void setLength(int length) {
        if (this.length ==length) return;
        this.length = length;
        this.calculateScale() ;
        pcs.firePropertyChange(Ruler.PROPERTY_LENGTH,null,null);
     }



    /**
     *  depend on setDisplayReange
     */
    protected  void calculateScale(){
        int length = this.getLength();
        ks=length/(this.getTickRange().getMax() -this.getTickRange().getMin()) ;
        //dp{
        if (DEBUG){
            System.out.println(this+"'ks:"+ks);
        }//dp}

    }


    protected  void setValueAt0(float valueAt0) {
        this.valueAt0 = valueAt0;
    }



        /*******************************************************************************************************
         *                GUI
         *******************************************************************************************************/
        /**
         *
         * @param g
         */
        public void paintComponent(Graphics g){
                ui.paintComponent(g);
        }


    /*******************************************************************************************************
     *                properties
     *******************************************************************************************************/
    /**************************************************
     *                ruler's basic properties
     ***************************************************/
    public void setLocation(Point location) {
        if (location==null||location.equals(this.location ))return;
        this.location = location;
        pcs.firePropertyChange(Ruler.PROPERTY_ORIENT,null,null);
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public Color getColor() {
        return color;
    }

    public int getLength() {
        return length;
    }

    public Range getTickRange() {
       /* if (this.tickRange ==null) {
          tickRange=new Extreme(100,0);
        }*/
        return tickRange;
    }

    public int getOrient() {
        return orient;
    }

    public void setOrient(int orient) {
        this.orient = orient;
        updateAscendOnOrient(orient);
        pcs.firePropertyChange(Ruler.PROPERTY_ORIENT,null,null);
    }

    private void updateAscendOnOrient(int orient) {

        if(orient==HORIZONTAL){//default behavior
                this.ascend =true;
        }
        else if(orient==VERTICAL){
            this.ascend =false;

        }
        this.updateValueAt0();//whenever ascend changed, need change value at 0.
    }

    public boolean isAscend() {
        return ascend;
    }

    public Point getLocation() {
        return location;
    }

    public float getKs() {
        return ks;
    }

    /**************************************************
     *                labels & ticks
     ***************************************************/
    //draw value of ticks
    public void setShowLabel(boolean showLabel) {
        this.showLabel = showLabel;
       // this.ui.setShowLabel(showLabel);
    }

    public int getLabelScale() {
        return ui.getLabelScale() ;
    }
    /**
     * The label of a value on the ruler can be scaled by 1000 (K) or 1,000,000 (M)
     * That is, if a label value is 1K, it is actually means 1000
     * @param labelScale
     * @see   <setDrawTick>
     */
    public void setLabelScale(int labelScale) {
        ui.setLabelScale(labelScale);
    }
     /**
     * draw ticks
      * @param showTick
     *  @see   <setLabelScale>
     */
    public void setShowTick(boolean showTick) {
        this.showTick=showTick;
    }
    public int getFormat() {
        return format;
    }

    public void setFormat(int format) {
        this.format = format;
        //ui.set
    }

    public boolean isShowTick() {
        return showTick;
    }

    public boolean isShowLabel() {
        return showLabel;
    }

    public BasicStroke getStroke() {
        return stroke;
    }

    public void setStroke(BasicStroke stroke) {
        this.stroke = stroke;
    }

    public void setLabelColor(Color labelColor) {
        this.labelColor = labelColor;
    }

    public void setLabelFont(Font labelFont) {
        this.labelFont = labelFont;
    }

    public Font getLabelFont() {
        return labelFont;
    }

    public Color getLabelColor() {
        return labelColor;
    }

    public float[] getTicksvalue() {
        return ticksvalue;
    }
    /**
     *  
     *  
     * @param ticksvalue
     */
    public void setTicksvalue(float[] ticksvalue) {

        Arrays.sort(ticksvalue);
        this.ticksvalue = ticksvalue;
        float min = ticksvalue[0];
        float max = ticksvalue[ticksvalue.length -1];
        this.setTickRange(min,max); // 
        pcs.firePropertyChange(Ruler.PROPERTY_TICKVALUES,null,null);
    }
    public void setNumOfSection(int numOfSection){
        ui.createSections(numOfSection);
    }
    /**
     *
      * @return all the ticks on the ruler
     */
    public Tick[] getTicks(){
        return ui.getTicks();
    }
    public void setTicklength(int ticklength){
        ui.setTicklength(ticklength);
    }
    /*******************************************************************************************************
     *                properties
     *******************************************************************************************************/

    public RulerUI getUI() {
        return ui;
    }

    public void setUI(RulerUI ui) {
        this.ui = ui;
    }
}


